#pragma once
#include "vector.h"

#define HAVEL 1
#define WALD 0

// ================================================================================================
// Basic ray tracer implementation;
// ================================================================================================
struct __declspec(align(16)) Ray {
    Vector4 origin;
    Vector4 direction;

    // -- mutator functions
    void SetDirection(float, float, float, float);
    void SetOrigin(float, float, float);

    Vector4 ComputeRay(float t);
    Vector4 ComputeRay(float t) const;
    
    // constructor
    Ray(Vector4, Vector4);
    Ray();
};

inline Ray::Ray() {
}

inline Ray::Ray(Vector4 origin_, Vector4 direction_) : origin(origin_), direction(direction_) {
} 

// ================================================================================================
// Mutator functions
// ================================================================================================
inline void Ray::SetDirection(float x, float y, float z, float w) {
    direction.x = x;
    direction.y = y;
    direction.z = z;
    direction.w = w;
}

inline void Ray::SetOrigin(float x, float y, float z) {
    direction.x = x;
    direction.y = y;
    direction.z = z;
}

// ================================================================================================
// Compute a ray with a given scalar value
// ================================================================================================
inline Vector4 Ray::ComputeRay(float t) {
    Vector4 destination = (direction * t) + origin;

    return destination;
}

inline Vector4 Ray::ComputeRay(float t) const {
    Vector4 destination = (direction * t) + origin;

    return destination;
}

struct __declspec(align(16)) RaySSE {
    float ox[4];
    float oy[4];
    float oz[4];
    float ow[4];

    float dx[4];
    float dy[4];
    float dz[4];
    float dw[4];

    float t[4];
};

// ================================================================================================
// Basic geometric primitives
// ================================================================================================
struct Plane {
    Vector4 origin;         //  really just some point on the plane
    Vector4 normal;         //  normal on the plane
};

struct Sphere {
    Vector4 center;         // sphere normal
    float radius;           // radius of the sphere 
};

#if WALD 

struct Triangle {
    Vector4 v0, v1, v2;     // vertices of the triangle
};

struct TriangleAcc { 
    // -- first 16 bytes
    float n_u;  // normal / normalk
    float n_v;  // normal / normal
    float n_d;  // constant of plane equation
    int k;      // projection dimension

    // -- constants used for bary
    // -- second 16 bytes
    float b_nu;
    float b_nv;
    float b_d;
    int pad1;  

    // -- third 16 bytes
    float c_nu;
    float c_nv;
    float c_d;
    int pad2;

    Vector4 normal;

}; 

#endif

#if HAVEL

struct Triangle {
    Vector4 v0, v1, v2;     // vertices of the triangle
};

struct _declspec(align(16)) TriangleAcc { 
    float nx, ny, nz, nd;
    float ux, uy, uz, ud;
    float vx, vy, vz, vd;

    void* operator new[](size_t num);
    void* operator new(size_t num);
    void operator delete(void* ptr);
    void operator delete[](void* ptr);
}; 

inline void* TriangleAcc::operator new(size_t num) {
    return _aligned_malloc(num, 16);
}

inline void* TriangleAcc::operator new[](size_t num) {
    return _aligned_malloc(num, 16);
}

inline void TriangleAcc::operator delete(void* ptr) {
    _aligned_free(ptr);
}

inline void TriangleAcc::operator delete[](void* ptr) {
    _aligned_free(ptr);
}

struct _declspec(align(16)) TriangleAccDP {
    float nx[4];
    float ny[4];
    float nz[4];
    float nd[4];
    float ux[4];
    float uy[4];
    float uz[4];
    float ud[4]; 
    float vx[4];
    float vy[4];
    float vz[4];
    float vd[4];

    void* operator new[](size_t num);
    void* operator new(size_t num);
    void operator delete(void* ptr);
    void operator delete[](void* ptr);
};

inline void* TriangleAccDP::operator new(size_t num) {
    void* mem = _aligned_malloc(num, 16);

    if(!mem) {
        throw std::bad_alloc();
    }

    return mem;
}

inline void* TriangleAccDP::operator new[](size_t num) {
    void* mem = _aligned_malloc(num, 16);

    if(!mem) {
        throw std::bad_alloc();
    }

    return mem;
}

inline void TriangleAccDP::operator delete(void* ptr) {
    _aligned_free(ptr);
}

inline void TriangleAccDP::operator delete[](void* ptr) {
    _aligned_free(ptr);
}

#endif

// ================================================================================================
// Simple point2d structure
// ================================================================================================
struct Point2D {
    uint x, y;
    Point2D operator+(const Point2D& pt);
    Point2D operator+=(const Point2D& pt);
    Point2D operator+(uint k);
};

// ================================================================================================
// Overloaded operator for the point
// ================================================================================================
inline Point2D Point2D::operator+(const Point2D& pt) {
    Point2D temp;
    temp.x = x + pt.x;
    temp.y = y + pt.y;

    return temp;
}

inline Point2D Point2D::operator+(uint k) {
    Point2D temp;
    temp.x = x + k;
    temp.y = y + k;

    return temp;
}

inline Point2D Point2D::operator+=(const Point2D& pt) {
    x += pt.x;
    y += pt.y;

    return *this;
}

// ================================================================================================
// Simple rectangle structure
// ================================================================================================
struct Rect2D {
    Point2D topleft;
    Point2D bottomright;
    Rect2D operator+(const Rect2D& rect);
    Rect2D operator+(const Point2D& pt);
    Rect2D operator+=(const Point2D& pt);
};

// ================================================================================================ 
// Overloaded operator for rectangle
// ================================================================================================ 
inline Rect2D Rect2D::operator+(const Rect2D& rect) {
    Rect2D temp;
    temp.topleft = topleft + rect.topleft;
    temp.bottomright = bottomright + rect.bottomright;

    return temp;
}

inline Rect2D Rect2D::operator+(const Point2D& pt) {
    Rect2D temp;
    temp.topleft = topleft + pt;
    temp.bottomright = bottomright + pt;

    return temp;
}

inline Rect2D Rect2D::operator+=(const Point2D& pt) {
    topleft += pt;
    bottomright += pt;

    return *this; 
}